﻿using AutoMapper;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MyCompany.MyProject.Common;
using MyCompany.MyProject.Common.Helper;
using MyCompany.MyProject.Extensions.Auth;
using MyCompany.MyProject.Extensions.Helper;
using MyCompany.MyProject.IService;
using MyCompany.MyProject.Model;
using MyCompany.MyProject.Model.DTO;
using MyCompany.MyProject.Model.Models;
using MyCompany.MyProject.Service;
using MyCompany.MyProject.WebApi.ModelDto.UserDto;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;

namespace MyCompany.MyProject.WebApi.Controllers
{
    /// <summary>
    /// 登录控制器
    /// </summary>
    [Route("api/[controller]/[action]")]
    [ApiController]
    [AllowAnonymous]
    public class LoginController : ControllerBase
    {
        private readonly IUserService userService;
        private readonly IBaseService<UserRole> userRoleService;
        private readonly PermissionRequirement requirement;
        private readonly IRoleModulePermissionServices _roleModulePermissionServices;
        private readonly IMapper _mapper;
        private readonly ILogger<LoginController> logger;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="userService"></param>
        /// <param name="userRoleService"></param>
        /// <param name="requirement"></param>
        /// <param name="roleModulePermissionServices"></param>
        /// <param name="mapper"></param>
        /// <param name="logger"></param>
        public LoginController(IUserService userService, IBaseService<UserRole> userRoleService,
            PermissionRequirement requirement, IRoleModulePermissionServices roleModulePermissionServices, IMapper mapper,
            ILogger<LoginController> logger)
        {
            this.userService = userService;
            this.userRoleService = userRoleService;
            this.requirement = requirement;
            _roleModulePermissionServices = roleModulePermissionServices;
            _mapper = mapper;
            this.logger = logger;
        }

        /// <summary>
        /// 登录
        /// </summary>
        /// <param name="loginParam"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<MessageModel<TokenInfoViewModel>> GetJwtToken(LoginParam loginParam)
        {
            string jwtStr = string.Empty;

            loginParam.Password = MD5Encrypt32(loginParam.Password);

            var user = await userService.Query(d => d.LoginName == loginParam.UserName && d.LoginPWD == loginParam.Password && d.IsDeleted == false);
            if (user.Count > 0)
            {
                var userRoles = await userService.GetUserRoleNameStr(loginParam.UserName, loginParam.Password);

                var claims = new List<Claim>
                {
                    new Claim(ClaimTypes.Name,loginParam.UserName),
                    //new Claim("uid",user.FirstOrDefault().Id.ToString()),
                    new Claim(JwtRegisteredClaimNames.Jti,user.FirstOrDefault().Id.ToString()),
                    new Claim("TenantId",user.FirstOrDefault().TenantId.ToString()),
                    new Claim(JwtRegisteredClaimNames.Iat,DateTime.Now.DateToTimeStamp()),
                    new Claim(ClaimTypes.Expiration,DateTime.Now.AddSeconds(requirement.Expiration.TotalSeconds).ToString())
                };

                claims.AddRange(userRoles.Split(',').Select(s => new Claim(ClaimTypes.Role, s)));

                var data = await _roleModulePermissionServices.RoleModuleMaps();
                var list = (from item in data
                            where item.IsDeleted == false
                            orderby item.Id
                            select new PermissionItem
                            {
                                Url = item.Modules?.LinkUrl,
                                Role = item.Role?.Name.ObjToString(),
                            }).ToList();

                requirement.Permissions = list;

                var token = JwtToken.BuildJwtToken(claims.ToArray(), requirement);

                return Success(token, "获取成功");
            }
            else
            {
                return Failed<TokenInfoViewModel>("认证失败");
            }
        }

        /// <summary>
        /// 刷新 token 令牌
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        [HttpGet]
        public async Task<MessageModel<TokenInfoViewModel>> RefreshToken(string token = "")
        {
            string jwtStr = string.Empty;
            if (string.IsNullOrEmpty(token))
            {
                return Failed<TokenInfoViewModel>("token 无效，请重新登录！");
            }
            var tokenModel = JwtHelper.SerializeJwt(token);
            if (tokenModel != null && JwtHelper.customSafeVerify(token) && tokenModel.Uid > 0)
            {
                var user = await userService.QueryById(tokenModel.Uid);
                var value = User.Claims.SingleOrDefault(s => s.Type == JwtRegisteredClaimNames.Iat)?.Value;
                if (value != null && user.CriticalModifyTime > value.ObjToDate())
                {
                    return Failed<TokenInfoViewModel>("很抱歉，授权已失效，请重新授权！");
                }
                if (user != null && !(value != null && user.CriticalModifyTime > value.ObjToDate()))
                {
                    var userRoles = await userService.GetUserRoleNameStr(user.LoginName, user.LoginPWD);
                    // 如果是基于用户的授权策略，这里要添加用户；如果是基于角色的授权策略，这里要添加角色
                    var claims = new List<Claim>
                        {
                            new Claim(ClaimTypes.Name,user.LoginName),
                            new Claim(JwtRegisteredClaimNames.Jti,tokenModel.Uid.ObjToString()),
                            new Claim(JwtRegisteredClaimNames.Iat,DateTime.Now.DateToTimeStamp()),
                            new  Claim(ClaimTypes.Expiration,DateTime.Now.AddSeconds(requirement.Expiration.TotalSeconds).ToString())
                        };
                    claims.AddRange(userRoles.Split(',').Select(s => new Claim(ClaimTypes.Role, s)));

                    // 用户标记
                    var identity = new ClaimsIdentity(JwtBearerDefaults.AuthenticationScheme);
                    identity.AddClaims(claims);

                    var refreshToken = JwtToken.BuildJwtToken(claims.ToArray(), requirement);
                    return Success(refreshToken, "获取刷新令牌成功");
                }
            }
            return Failed<TokenInfoViewModel>("获取刷新令牌失败");
        }

        /// <summary>
        /// 根据用户token 获取用户信息
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        [HttpGet]
        [Authorize]
        public async Task<MessageModel<UserVo>> GetInfoByToken(string token)
        {
            var data = new MessageModel<UserVo>();
            if (!string.IsNullOrEmpty(token))
            {
                var tokenModel = JwtHelper.SerializeJwt(token);
                if (tokenModel != null && tokenModel.Uid > 0)
                {
                    var userinfo = await userService.QueryById(tokenModel.Uid);
                    if (userinfo != null)
                    {
                        data.response = _mapper.Map<UserVo>(userinfo);
                        data.success = true;
                        data.msg = "获取成功";
                    }
                }
            }

            return data;
        }


        /// <summary>
        /// 更新用户信息
        /// </summary>
        /// <param name="userVo"></param>
        /// <returns></returns>
        [HttpPut]
        [Authorize]
        public async Task<MessageModel<string>> UpdateUserInfo(UserVo userVo)
        {
            var data = new MessageModel<string>();
            var oldUser = await userService.QueryById(userVo.Id);
            if (oldUser == null)
            {
                return Failed<string>("用户不存在或已被删除");
            }
            oldUser.UpdateTime = DateTime.Now;
            oldUser.LoginPWD = MD5Helper.MD5Encrypt32(userVo.LoginPWD);
            oldUser.RealName = userVo.RealName;
            oldUser.Remark = userVo.Remark;

            data.success = await userService.Update(oldUser);
            if (data.success)
            {
                data.msg = "更新成功";
            }
            return data;
        }


        private MessageModel<T> Success<T>(T data, string msg = "成功")
        {
            return new MessageModel<T>()
            {
                success = true,
                msg = msg,
                response = data,
            };
        }
        private MessageModel<T> Failed<T>(string msg = "失败", int status = 500)
        {
            return new MessageModel<T>()
            {
                success = false,
                status = status,
                msg = msg,
                response = default,
            };
        }
        private string MD5Encrypt32(string password = "")
        {
            string pwd = string.Empty;
            try
            {
                if (!string.IsNullOrWhiteSpace(password))
                {
                    // 实例化 MD5 对象
                    MD5 md5 = MD5.Create();
                    // 加密后是一个字符型的数组，这里需要注意编码 UTF8/Unicode 等
                    byte[] s = md5.ComputeHash(Encoding.UTF8.GetBytes(password));
                    // 通过使用循环，将字节类型的数组转化为字符串，此字符串是常规字符格式化所得
                    foreach (var b in s)
                    {
                        // 将得到的字符串使用十六进制类型格式，格式后的字符是小写的字母，如果使用大写（X）则格式后的字符是大写字符 
                        pwd = string.Concat(pwd, b.ToString("X2"));
                    }
                }
            }
            catch
            {
                throw new Exception($"错误的 password 字符串：【{password}】");
            }
            return pwd;
        }
    }

    /// <summary>
    /// 登录参数
    /// </summary>
    public class LoginParam
    {
        /// <summary>
        /// 用户名
        /// </summary>
        public string UserName { get; set; }

        /// <summary>
        /// 密码
        /// </summary>
        public string Password { get; set; }
    }
}
